Dieses Notebook bereitet die Daten für die Intelligent Zoning Engine vor. Es speichert

Bereits in anderen scripten wurde vorbereitet:

Die Daten werden wiefolgt vorbereitet:

TODO: - die sozioökonomischen Faktoren werden aus den Wahlbezirken auf die Blöcke hochgerechnet (https://github.com/berlinermorgenpost/cogran)

Laden der Daten

sampled_buildings = read_rds('output/sampled_buildings.rds')
bez = readOGR('download/RBS_OD_BEZ_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_BEZ_2015_12.geojson", layer: "OGRGeoJSON"
with 13 features
It has 2 fields
blk = readOGR('download/RBS_OD_BLK_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_BLK_2015_12.geojson", layer: "OGRGeoJSON"
with 15720 features
It has 4 fields
lor = readOGR('download/RBS_OD_LOR_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_LOR_2015_12.geojson", layer: "OGRGeoJSON"
with 447 features
It has 8 fields
re_schulstand = readOGR('download/re_schulstand.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/re_schulstand.geojson", layer: "OGRGeoJSON"
with 709 features
It has 20 fields
HKO_2015 = readOGR('download/HKO_2015.geojson', 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/HKO_2015.geojson", layer: "OGRGeoJSON"
with 306019 features
It has 4 fields

Schulwege

route_matrix = read_rds('output/route_matrix.rds')

Schulkapazitäten und Einwohnerzahler auf LOR-Ebene

kapas = read_csv('download/anmeldezahlen.csv') %>% filter(grepl('G', Schulnummer)) %>% filter(!is.na(`Plätze`))
Parsed with column specification:
cols(
  Bezirk = col_character(),
  Schulnummer = col_character(),
  Schulname = col_character(),
  Plätze = col_integer(),
  Anmeldungen = col_character()
)
einwohner_lor = read_delim('download/EWR201512E_Matrix.csv', delim=';')
Parsed with column specification:
cols(
  .default = col_character(),
  ZEIT = col_integer(),
  STADTRAUM = col_integer(),
  E_E = col_number(),
  E_EM = col_number(),
  E_EW = col_number(),
  E_E50_55 = col_number(),
  E_E25U55 = col_number(),
  E_E55U65 = col_number(),
  E_E65U80 = col_number()
)
See spec(...) for full column specifications.

Überprüfung der Vollständigkeit der Daten über Anmeldezahlen/Kapazitäten

re_schulstand_df = re_schulstand %>% as.data.frame() %>% rename(lon=coords.x1, lat=coords.x2) %>% cbind(over(re_schulstand, lor))
re_schulstand_df %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>%
  group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
  rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n()))
Joining, by = "Bezirk"

Für welche Bezirke haben wir für alle Schulen Kapazitäten gegeben?

re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>% group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
  rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n())) %>% filter(`Anzahl Schulen` == `Mit Kapazität`)
Joining, by = "Bezirk"

Überprüfung ob die Liste der Schulen und Liste der Schulen mit Kapazitätsinformationen gleich sind:

bezirk = 'Tempelhof-Schöneberg'
schulen_mit_kapa = kapas %>% filter(Bezirk == bezirk) %>% .$Schulnummer
schulen_mit_kapa
 [1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
 [9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01

07G02

07G03

07G05

07G06

07G07

07G10

07G12

07G13

07G14

07G15

07G16

07G17

07G18

07G19

07G20

07G21

07G22

07G23

07G24

07G25

07G26

07G27

07G28

07G29

07G30

07G31

07G32

07G34

07G35

07G36

07G37
grundschulen = re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK == bezirk) %>% .$spatial_name
'In Anmeldeliste, fehlt in Schulstand'
[1] "In Anmeldeliste, fehlt in Schulstand"
In Anmeldeliste, fehlt in Schulstand
setdiff(schulen_mit_kapa, grundschulen)
character(0)
'In re_schulstand, fehlt in Anmeldeliste'
[1] "In re_schulstand, fehlt in Anmeldeliste"
In re_schulstand, fehlt in Anmeldeliste
setdiff(grundschulen, schulen_mit_kapa)
character(0)
map = get_map('Berlin')
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=Berlin&zoom=10&size=640x640&scale=2&maptype=terrain&language=en-EN&sensor=false
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Berlin&sensor=false
re_schulstand_df_w_kapas = re_schulstand_df %>% left_join(kapas, by=c('spatial_name'='Schulnummer'))

Plot aller Schulen, mit der Info, ob Kapazitätsinformationen verfügbar sind.

data = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk) %>% mutate(missing.capa=is.na(`Plätze`))
ggmap(map) + geom_point(aes(lon, lat, color=missing.capa), data=data) +
    coord_map(xlim=c(min(data$lon)-0.01, max(data$lon)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Filter auf Schulen mit Kapazitätsinformationen (für T-S sind das alle):

relevant_schools = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk & !is.na(`Plätze`)) %>% .$spatial_name
relevant_schools
 [1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
 [9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01

07G02

07G03

07G05

07G06

07G07

07G10

07G12

07G13

07G14

07G15

07G16

07G17

07G18

07G19

07G20

07G21

07G22

07G23

07G24

07G25

07G26

07G27

07G28

07G29

07G30

07G31

07G32

07G34

07G35

07G36

07G37

Mapping Bezirk->LOR->Block

df_bez = as.data.frame(bez)
df_lor = as.data.frame(lor)
df_blk = as.data.frame(blk)

Sanity-Check: LORs und Blöcke im Bezirk

bez_id = filter(df_bez, BEZNAME == bezirk)$BEZ
relevant_lors = df_lor %>% filter(BEZ == bez_id)
relevant_blks = df_blk %>% filter(BEZ == bez_id)
ggplot() + geom_path(aes(x=long, y=lat, group=group), data=lor[lor$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez, color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Blöcke im Bezirk

ggplot() + geom_path(aes(x=long, y=lat, group=group), data=blk[blk$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez[bez$BEZ == bez_id,], color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Kinder im Bezirk auf Blöcke hochrechnen

Über die Einwohnerinformationen in RBS_OD_BLK_2015_12.geojson kann EWR201512E_Matrix.csv von LOR-Ebene auf Blockebene hochgerechnet werden.

TODO: Stattdessen mit https://github.com/berlinermorgenpost/cogran machen?

Plot der 6-Jährigen nach EWR201512E_Matrix.csv

Wir verwenden das mittel der 5- und 6-Jährigen.

TODO neue Daten von Torres? TODO Prognose?

relevant_ewr = einwohner_lor %>%
  select(RAUMID, E_E05_06, E_E06_07) %>%
  filter(RAUMID %in% relevant_lors$PLR) %>%
  # Schnitt der 5 und 6-Jährigen
  mutate(kids=(as.numeric(gsub(',','.',E_E06_07))+as.numeric(gsub(',','.',E_E05_06)))/2) %>% as.data.frame()

data = tidy(lor[lor$BEZ == bez_id,], region='PLR') %>% inner_join(relevant_ewr, by=c('id'='RAUMID'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Plot der Einwohner auf Blockebene

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(df_blk, by=c('id'='BLK')) %>% mutate(Einw=ifelse(Einw==0, NA, Einw))
0
[1] 0
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=Einw), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Hochrrechnung auf Blöcke, Strukturquote

Strukturquote = 0.9

kids_in_blks = relevant_blks %>%
  group_by(PLR) %>%
  mutate(EinwRatio = Einw/sum(Einw)) %>%
  ungroup %>%
  left_join(relevant_ewr, by=c('PLR'='RAUMID')) %>%
  mutate(kids = EinwRatio*kids) %>%
  mutate(kids = Strukturquote*kids) %>% # Strukturquote
  select(BEZ, PLR, BLK, Einw, kids) %>%
  as.data.frame()
row.names(kids_in_blks) = kids_in_blks$BLK

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(kids_in_blks, by=c('id'='BLK')) %>% mutate(kids=ifelse(kids==0, NA, kids), Einw=ifelse(Einw==0, NA, Einw))

ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Verfügbare Plätze

relevant_kapas = kapas %>% select(Schulnummer, Kapa=`Plätze`) %>% filter(Schulnummer %in% relevant_schools) %>% as.data.frame()
#row.names(relevant_kapas) = relevant_kapas$Schulnummer

Überprüfung der Summe der Kapazitäten, Anmeldungen und Kinderstatistiken

'Summe Kapas'
[1] "Summe Kapas"
Summe Kapas
relevant_kapas %>% .$Kapa %>% sum
[1] 2584
'Anmeldungen'
[1] "Anmeldungen"
Anmeldungen
kapas %>% mutate(Anmeldungen = as.numeric(gsub('[^0-9]', '', Anmeldungen))) %>% filter(Schulnummer %in% relevant_schools) %>% .$Anmeldungen %>% sum
[1] 2752
'Kids laut Statistik'
[1] "Kids laut Statistik"
Kids laut Statistik
kids_in_blks$kids %>% sum
[1] 2620.8
relevant_ewr$kids %>% sum
[1] 2912

Schulwege von Blöcken zu Schulen aggregieren

Für jedes Wohngebäude suchen wir den zugehörigen Block

residential_buildings_blocks = sampled_buildings %>% inner_join(df_blk) %>% filter(BEZ == bez_id)
Joining, by = "BLK"
residential_buildings_blocks
routes_from_blks = residential_buildings_blocks %>%
  left_join(route_matrix %>% filter(dst %in% relevant_schools), by=c('OI'='src'))
head(routes_from_blks)

Plot der relevanten Blöcke (mit Wohngebäuden)

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(routes_from_blks %>% group_by(BLK) %>% summarise(n=n()), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group), fill='red', data=data) +
  #geom_point(aes(x=lon, y=lat), data=rb_df, color='black', size=0.01) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_from_blks = routes_from_blks %>% as.data.frame() %>% group_by(BLK, dst) %>% summarise(min=min(distance), avg=mean(distance), med=median(distance), max=max(distance)) %>% ungroup
travel_from_blks

Plot der Blöcke mit Färbung nach durchschnittlichem Weg zur nächsten Schule

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(travel_from_blks %>% group_by(BLK) %>% top_n(1, -avg), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=-avg), data=data) +
  geom_point(aes(lon, lat), color='red', data = re_schulstand_df %>% filter(BEZIRK==bezirk & SCHULART=='Grundschule')) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_matrix = travel_from_blks %>% select(BLK, dst, avg) %>% spread(dst, avg)
dim(travel_matrix)
[1] 1012   33
travel_matrix

Sozioökonomische Daten

Wir haben Sozioökonomische Daten in den Wahlbezirken. Strategie: - Schneiden der Wahlbezirke mit den Blöcken - Übernahme des Prozentwertes vom Wahlbezirk für jeden (Unter-)block - Zuordnung zu jedem Block und Vereinigung durch Flächen/Wohnhaus-gewichtetes Mittel des Prozentwertes

UWB = readOGR('download/RBS_OD_UWB_AGH2016.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_UWB_AGH2016.geojson", layer: "OGRGeoJSON"
with 1779 features
It has 4 fields
UWB$ID = paste0(UWB$BEZ, UWB$UWB)
sozio_UWB = read_excel('download/DL_BE_AH2016_Strukturdaten.xlsx', sheet = 3) %>% select(ID, sgbIIu65=`Einwohner unter 65 in SGB II 2014 Prozent`)
UWB = UWB %>% sp::merge(sozio_UWB, by='ID')

ggplot(broom::tidy(UWB, region='ID') %>% inner_join(UWB %>% as.data.frame, by=c('id'='ID'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Check for self intersections

wgs84 = CRS(proj4string(blk))
ea_projection = CRS("+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
blk_in_bez = blk[blk$BEZ == bez_id,]
gIsValid(blk_in_bez)
[1] TRUE
blk_in_bez = gBuffer(spTransform(blk_in_bez, ea_projection), byid=T, width = -0.1)
any(xor(gIntersects(blk_in_bez, byid=T, returnDense = T), diag(1, length(blk_in_bez)) == 1))
[1] FALSE
plot(blk_in_bez)

#gIntersects(UWB[UWB$BEZ == '07',], byid=TRUE)
uwb_in_bez = gBuffer(spTransform(UWB[UWB$BEZ == '07',], ea_projection), byid=T, width=-0.1)
gIsValid(uwb_in_bez)
[1] TRUE
any(xor(gIntersects(uwb_in_bez, byid=T, returnDense = T), diag(1, length(uwb_in_bez)) == 1))
[1] FALSE
plot(uwb_in_bez)

intersection = gIntersection(uwb_in_bez, blk_in_bez, byid = T, drop_lower_td = T)

gIsValid(intersection)
[1] TRUE
plot(intersection)

intersection_uwb_data = intersection %>% over(uwb_in_bez)
intersection_blk_data = intersection %>% over(blk_in_bez)
intersection_area = gArea(intersection, byid=T)
intersection_data = cbind(
    intersection_uwb_data %>% select(UWB_ID=ID, sgbIIu65),
    intersection_blk_data %>% select(BLK, BLK_Einw=Einw),
    data.frame(area=intersection_area) # FIXME how else to normalize?
    )
length(intersection)
[1] 1219
nrow(blk_in_bez)
[1] 1201
# did we miss any blocks?
setdiff(blk_in_bez$BLK, intersection_data$BLK)
character(0)

Pro Block mische die SGB-Werte der Unterblöcke gewichtet nach Fläche. FIXME - besser nach Anzahl der Wohnhäuser?

sgbII_blk = intersection_data %>%
  group_by(BLK) %>%
  summarise(sgbIIu65=sum(sgbIIu65*area)/sum(area)/100)
ggplot(broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK') %>% left_join(sgbII_blk, by=c('id'='BLK'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Adjazenz von Blöcken

row.names(blk_in_bez) = blk_in_bez$BLK
buffeded_blk_in_bez = gBuffer(blk_in_bez, byid=T, width=40)
adjacency = gIntersects(gBuffer(blk_in_bez, byid=T, width=40), byid = T)
rownames(adjacency) = blk_in_bez$BLK
colnames(adjacency) = blk_in_bez$BLK

adjacency_df = adjacency %>% as.data.frame() %>% mutate(from=rownames(.)) %>%
  gather(to, connected, -from) %>% filter(connected) %>%
  inner_join(spTransform(blk_in_bez, wgs84) %>% coordinates() %>% as.data.frame() %>% rename(from_long=V1, from_lat=V2) %>% mutate(from=rownames(.))) %>%
  inner_join(spTransform(blk_in_bez, wgs84) %>% coordinates() %>% as.data.frame() %>% rename(to_long=V1, to_lat=V2) %>% mutate(to=rownames(.)))
Joining, by = "from"
Joining, by = "to"
ggplot() +
  geom_polygon(aes(x=long, y=lat, group=group), fill='gray', data=broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK')) + 
  geom_segment(aes(x=from_long, y=from_lat, xend=to_long, yend=to_lat), size=0.1, color='black', data=adjacency_df) +
  theme_nothing() + coord_map()
Warning: `panel.margin` is deprecated. Please use `panel.spacing` property
instead

ggsave('figs/adjacency.pdf')
Saving 7 x 5 in image
adjacency_df %>% filter(connected & from != to) %>% select(from, to) %>% write_csv('app/data/adjacency.csv')

Select relevant data

optim_kapas = relevant_kapas
optim_kids_in_blks = kids_in_blks %>% filter(kids > 0) %>% inner_join(travel_matrix, by='BLK') %>% select(BLK, kids) %>% mutate(kids=kids)
nrow(optim_kids_in_blks)
[1] 1012
nrow(optim_kapas)
[1] 32
select_schools = as.character(optim_kapas$Schulnummer)
select_blks = as.character(optim_kids_in_blks$BLK)

optim_matrix = inner_join(optim_kids_in_blks, travel_matrix, by='BLK')[select_schools]

dim(optim_matrix)
[1] 1012   32
optim_kapas$Kapa %>% sum
[1] 2584
optim_kids_in_blks$kids %>% sum
[1] 2611.995

Naive Zuordnung: Jeder Block zur nächsten Schule

solution = optim_matrix %>% mutate(BLK=optim_kids_in_blks$BLK) %>% gather(school, dist, -BLK) %>% group_by(BLK) %>% top_n(1, -dist) %>% ungroup

optim_matrix %>% t %>% as.data.frame %>% summarise_each(funs(min)) %>% sum()
[1] 781609.6
solines = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school')) %>% inner_join(cbind(as.data.frame(coordinates(blk[blk$BEZ == bez_id,])), blk[blk$BEZ == bez_id,]@data['BLK']))
Joining, by = "BLK"
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(solution, by=c('id'='BLK'))
ggmap(map, darken = c(0.5, 'white')) + geom_polygon(aes(x=long, y=lat, group=group, fill=school), data=data) +
  geom_segment(aes(x=V1,y=V2,xend=lon,yend=lat), data=solines, size=0.3) +
  geom_point(aes(lon, lat), color='black', size=2, data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  geom_point(aes(lon, lat, color=spatial_name), data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01)) +
  guides(color=F, fill=F)

Darstellung der Zuordnung als Tabelle

library(formattable)
solution %>% inner_join(optim_kids_in_blks, by='BLK') %>% inner_join(travel_from_blks, by=c('BLK'='BLK', 'school'='dst')) %>%
  group_by(school) %>% summarise(
    kids=sum(kids),
    num_blocks=n(),
    min_dist=min(min),
    avg_dist=mean((kids*avg)/sum(kids)),
    max_dist=max(max)
  ) %>%
  inner_join(relevant_kapas, by=c('school'='Schulnummer')) %>%
  mutate(
    utilization=kids/Kapa
  ) %>% select(
   Schule=school,
   `Blöcke`=num_blocks,
   Kapazität=Kapa,
   Kinder=kids,
   Auslastung=utilization,
   `Weg (min)`=min_dist,
   `Weg (Ø)`=avg_dist,
   `Weg (max)`=max_dist
  ) %>%
  formattable(
    list(
      Kinder = formatter("span", x ~ digits(x, 2)),
      Auslastung = formatter("span",
        style = x ~ style(color = ifelse(x < 1, "green", "red")),
        x ~ icontext(ifelse(x < 1, "ok", "remove"), percent(x))
      ),
      `Weg (Ø)` = proportion_bar("lightblue"),
      `Weg (min)` = proportion_bar("lightblue"),
      `Weg (max)` = proportion_bar("lightblue")
    )
  )
Schule Blöcke Kapazität Kinder Auslastung Weg (min) Weg (Ø) Weg (max)
07G01 10 66 49.42 74.87% 287.933136 603.5498 1157.8849
07G02 19 98 43.55 44.44% 59.205467 435.8940 896.3418
07G03 14 73 56.66 77.61% 70.582603 411.5156 1037.2817
07G05 19 78 90.09 115.50% 73.309372 538.3502 962.0107
07G06 29 55 79.84 145.17% 241.365601 806.7650 1291.6294
07G07 10 81 16.10 19.88% 159.838760 585.8896 1359.2705
07G10 32 112 152.98 136.59% 88.422501 656.5627 1520.3507
07G12 12 75 33.54 44.72% 29.084946 360.0576 646.2440
07G13 28 82 133.36 162.63% 6.723136 508.4283 999.8708
07G14 32 78 76.44 98.00% 111.006302 438.5092 779.3000
07G15 49 104 209.08 201.04% 87.995552 829.5834 1612.8036
07G16 21 104 55.63 53.49% 139.662811 577.2329 1010.9780
07G17 24 100 70.58 70.58% 51.495129 419.9221 713.7672
07G18 20 44 65.93 149.83% 67.150185 358.2698 642.9683
07G19 32 112 97.54 87.09% 116.220581 1114.9476 2214.6074
07G20 48 81 160.35 197.97% 75.543091 684.0351 1458.9825
07G21 23 72 69.54 96.58% 180.236908 499.9357 1312.0090
07G22 28 91 70.37 77.33% 136.316772 630.6502 1490.5638
07G23 9 50 21.91 43.82% 188.932098 732.4195 1194.2965
07G24 26 75 70.58 94.11% 164.014404 614.9176 1473.8057
07G25 36 75 122.51 163.35% 40.946930 592.4875 1160.8953
07G26 26 52 35.74 68.73% 129.166183 583.6184 1078.1425
07G27 19 75 46.20 61.60% 38.674854 691.0156 1295.4709
07G28 60 75 81.88 109.17% 119.915337 961.6523 1791.4529
07G29 95 104 113.15 108.80% 16.785637 966.7198 2026.0449
07G30 44 72 75.78 105.25% 135.504837 875.0386 2417.1389
07G31 20 78 46.84 60.05% 176.327759 851.4545 1529.7749
07G32 60 78 55.53 71.19% 190.077850 738.2852 1332.8569
07G34 41 100 178.11 178.11% 156.965714 1145.5052 2229.8320
07G35 34 75 88.26 117.68% 130.356995 764.2659 1534.7168
07G36 35 100 56.39 56.39% 190.601837 1010.8961 2341.1589
07G37 57 69 88.14 127.75% 2.115040 1243.0577 2116.3857

Daten für die App speichern

Neue Daten

Entities / Schulen

entities = subset(re_schulstand_df, spatial_name %in% relevant_schools) %>%
  rename(entity_id = spatial_name) %>%
  select(-gml_id, -spatial_alias, -spatial_type) %>%
  inner_join(rename(relevant_kapas, capacity=Kapa), by=c('entity_id'='Schulnummer'))
coordinates(entities) = ~ lon + lat
proj4string(entities) = CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
file.remove('app/data/entities.geojson')
[1] TRUE
entities %>% writeOGR('app/data/entities.geojson', layer="entities", driver="GeoJSON", check_exists=F)
entities@data %>% select(entity_id, capacity) %>% write_csv('app/data/entities.csv')

Units / Statistische Blöcke

units = subset(blk, BEZ == bez_id)

units@data$PLR = NULL
units@data$Einw = NULL
units@data$BEZ = NULL
units@data$unit_id = units@data$BLK
units@data$BLK = NULL

units = units %>% sp::merge(
  optim_kids_in_blks %>%
    left_join(sgbII_blk) %>%
    select(unit_id=BLK, population=kids, sgbIIu65)
  )
Joining, by = "BLK"
file.remove('app/data/units.geojson')
[1] TRUE
units %>% writeOGR('app/data/units.geojson', layer="units", driver="GeoJSON", check_exists=F)
units@data %>% write_csv('app/data/units.csv')

Zuordnung

assignment = solution %>% select(unit_id=BLK, entity_id=school)
assignment %>% write_csv('app/data/assignment.csv')

Weights / Schulwege

travel_from_blks %>%
  rename(unit_id=BLK, entity_id=dst) %>%
  #gather(weight, value, -unit_id, -entity_id) %>%
  write_csv('app/data/weights.csv')

Zusätzliche Daten

file.copy('download/RBS_OD_BEZ_2015_12.geojson', 'app/data/RBS_OD_BEZ_2015_12.geojson')
[1] FALSE
addresses_in_bez = HKO_2015[!is.na(over(HKO_2015, subset(blk, BEZ == bez_id))$BEZ),c('STN', 'HNR')]
names(addresses_in_bez) = c('street', 'no')
addresses_in_bez@data
file.remove('app/data/addresses.geojson')
[1] TRUE
writeOGR(addresses_in_bez, "app/data/addresses.geojson", layer="addresses", driver="GeoJSON", check_exists = FALSE)
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3IgbGlicywgaW5jbHVkZT1GLCB3YXJuaW5nPUZ9CmxpYnJhcnkocmVhZHIpCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KHJnZGFsKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dtYXApCmxpYnJhcnkocHVycnIpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkobWFwdG9vbHMpCmxpYnJhcnkocmdlb3MpCmBgYAoKRGllc2VzIE5vdGVib29rIGJlcmVpdGV0IGRpZSBEYXRlbiBmw7xyIGRpZSBJbnRlbGxpZ2VudCBab25pbmcgRW5naW5lIHZvci4gRXMgc3BlaWNoZXJ0CgotIGVudGl0aWVzLmdlb2pzb24g4oCUIFNjaHVsZW4sIGRlcmVuIEdlb2tvb3JkaW5hdGVuIHVuZCBBdHRyaWJ1dGU6IGVudGl0eV9pZCwgY2FwYWNpdHksIHVuZCBhbmRlcmUgQXR0cmlidXRlCi0gZW50aXRpZXMuY3N2IOKAlCBTdGF0aXN0aXNjaGUgQmzDtmNrZSBCZXJsaW5zIHVuZCBvcHRpbWllcnVuZ3NyZWxldmFudGUgQXR0cmlidXRlOiBlbnRpdHlfaWQsIGNhcGFjaXR5Ci0gdW5pdHMuZ2VvanNvbiDigJQgU3RhdGlzdGlzY2hlIEJsw7Zja2UgQmVybGlucywgZGVyZW4gR2VvbWV0cmllIHVuZCBBdHRyaWJ1dGUKLSB1bml0cy5jc3Yg4oCUIFN0YXRzaXRpc2NoZSBCbMO2Y2tlIEJlcmxpbnMgdW5kIG9wdGltaWVydW5nc3JlbGV2YW50ZSBBdHRyaWJ1dGU6IHVuaXRfaWQsIHBvcHVsYXRpb24sIHBlcmNlbnRhZ2Vfc2diCi0gd2VpZ2h0cy5jc3Yg4oCUIG9wdGltaWVydW5nc3JlbGV2YW50ZSBHZXdpY2h0ZSB3aWUgRnXDn3dlZ2UgLyBTcGFsdGVuOiBlbnRpdHlfaWQsIHVuaXRfaWQsIHdlaWdodCwgdmFsdWUKLSBhc3NpZ25tZW50LmNzdiDigJQgZWluZSBpbml0aWFsZSBadW9yZG51bmcgLyBTcGFsdGVuOiB1bml0X2lkLCBlbnRpdHlfaWQKCkJlcmVpdHMgaW4gYW5kZXJlbiBzY3JpcHRlbiB3dXJkZSB2b3JiZXJlaXRldDoKCi0gRnXDn3dlZ2VuIHZvbiBlaW5lciBncm/Dn2VuIFNpY2hwcm9iZSB2b24gKF9Xb2huXy0pR2Viw6R1ZGVuIHp1IGFsbGVuIFNjaHVsZW4gd3VyZGVuIGJlcmVjaG5ldCB1bmQgaW4gYHJvdXRlX21hdHJpeC5jc3ZgIGdlc3BlaWNoZXJ0Ci0gRGllIFN0aWNocHJvYmUgd3VyZGUgaW4gYHNhbXBsZWRfYnVpbGRpbmdzLmNzdmAgZ2VzcGVpY2hlcnQKCkRpZSBEYXRlbiB3ZXJkZW4gd2llZm9sZ3Qgdm9yYmVyZWl0ZXQ6CgotIHBybyBCbG9jayB3ZXJkZW4gZGllIEFuemFobCBkZXIgZWluenVzY2h1bGVuZGVuIEtpbmRlciBtaXQgSGlsZmUgZGVyIEVpbndvaG5lcnphaGxlbiBuYWNoIEFsdGVyIGF1ZiBMT1ItRWJlbmUgaW4gYEVXUjIwMTUxMkVfTWF0cml4LmNzdmAgaG9jaGdlcmVjaG5ldAogICAgLSBLaW5kZXIgZGVzIExPUiB3ZXJkZW4gQW50ZWlsaWcgbmFjaCBFaW53b2huZXJ6YWhsIGRlcyBCbG9ja3MgaW0gVmVyaMOkbHRuaXMgenVtIExPUiBhdWYgZGllIEJsw7Zja2UgdmVydGVpbHQKLSBlcyB3ZXJkZW4gbWluaW1hbGUsIGR1cmNoc2Nobml0dGxpY2hlIHVuZCBtYXhpbWFsZSBGdcOfd2VnZSBhdXMgamVkZW0gQmxvY2sgZXJyZWNobmV0CgpUT0RPOgotIGRpZSBzb3ppb8O2a29ub21pc2NoZW4gRmFrdG9yZW4gd2VyZGVuIGF1cyBkZW4gV2FobGJlemlya2VuIGF1ZiBkaWUgQmzDtmNrZSBob2NoZ2VyZWNobmV0IChodHRwczovL2dpdGh1Yi5jb20vYmVybGluZXJtb3JnZW5wb3N0L2NvZ3JhbikKCiMjIExhZGVuIGRlciBEYXRlbgoKYGBge3IgbG9hZCBkYXRhLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpzYW1wbGVkX2J1aWxkaW5ncyA9IHJlYWRfcmRzKCdvdXRwdXQvc2FtcGxlZF9idWlsZGluZ3MucmRzJykKYmV6ID0gcmVhZE9HUignZG93bmxvYWQvUkJTX09EX0JFWl8yMDE1XzEyLmdlb2pzb24nLCBsYXllciA9ICdPR1JHZW9KU09OJywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpibGsgPSByZWFkT0dSKCdkb3dubG9hZC9SQlNfT0RfQkxLXzIwMTVfMTIuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmxvciA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9MT1JfMjAxNV8xMi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKcmVfc2NodWxzdGFuZCA9IHJlYWRPR1IoJ2Rvd25sb2FkL3JlX3NjaHVsc3RhbmQuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCkhLT18yMDE1ID0gcmVhZE9HUignZG93bmxvYWQvSEtPXzIwMTUuZ2VvanNvbicsICdPR1JHZW9KU09OJywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpgYGAKCiMjIyBTY2h1bHdlZ2UKCmBgYHtyfQpyb3V0ZV9tYXRyaXggPSByZWFkX3Jkcygnb3V0cHV0L3JvdXRlX21hdHJpeC5yZHMnKQpgYGAKCiMjIyBTY2h1bGthcGF6aXTDpHRlbiB1bmQgRWlud29obmVyemFobGVyIGF1ZiBMT1ItRWJlbmUKCmBgYHtyfQprYXBhcyA9IHJlYWRfY3N2KCdkb3dubG9hZC9hbm1lbGRlemFobGVuLmNzdicpICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBTY2h1bG51bW1lcikpICU+JSBmaWx0ZXIoIWlzLm5hKGBQbMOkdHplYCkpCmVpbndvaG5lcl9sb3IgPSByZWFkX2RlbGltKCdkb3dubG9hZC9FV1IyMDE1MTJFX01hdHJpeC5jc3YnLCBkZWxpbT0nOycpCmBgYAoKIyMgw5xiZXJwcsO8ZnVuZyBkZXIgVm9sbHN0w6RuZGlna2VpdCBkZXIgRGF0ZW4gw7xiZXIgQW5tZWxkZXphaGxlbi9LYXBheml0w6R0ZW4KCmBgYHtyfQpyZV9zY2h1bHN0YW5kX2RmID0gcmVfc2NodWxzdGFuZCAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSByZW5hbWUobG9uPWNvb3Jkcy54MSwgbGF0PWNvb3Jkcy54MikgJT4lIGNiaW5kKG92ZXIocmVfc2NodWxzdGFuZCwgbG9yKSkKcmVfc2NodWxzdGFuZF9kZiAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIG11dGF0ZShCRVpJUks9ZW5jMnV0ZjgoYXMuY2hhcmFjdGVyKEJFWklSSykpKSAlPiUKICBncm91cF9ieShCRVpJUkspICU+JSBzdW1tYXJpc2UoYEFuemFobCBTY2h1bGVuYCA9IG4oKSkgJT4lCiAgcmVuYW1lKEJlemlyaz1CRVpJUkspICU+JSBsZWZ0X2pvaW4oa2FwYXMgJT4lIGdyb3VwX2J5KEJlemlyaykgJT4lIHN1bW1hcmlzZShgTWl0IEthcGF6aXTDpHRgID0gbigpKSkKYGBgCgpGw7xyIHdlbGNoZSBCZXppcmtlIGhhYmVuIHdpciBmw7xyIGFsbGUgU2NodWxlbiBLYXBheml0w6R0ZW4gZ2VnZWJlbj8KCmBgYHtyfQpyZV9zY2h1bHN0YW5kICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIGZpbHRlcihncmVwbCgnRycsIHNwYXRpYWxfbmFtZSkpICU+JSBtdXRhdGUoQkVaSVJLPWVuYzJ1dGY4KGFzLmNoYXJhY3RlcihCRVpJUkspKSkgJT4lIGdyb3VwX2J5KEJFWklSSykgJT4lIHN1bW1hcmlzZShgQW56YWhsIFNjaHVsZW5gID0gbigpKSAlPiUKICByZW5hbWUoQmV6aXJrPUJFWklSSykgJT4lIGxlZnRfam9pbihrYXBhcyAlPiUgZ3JvdXBfYnkoQmV6aXJrKSAlPiUgc3VtbWFyaXNlKGBNaXQgS2FwYXppdMOkdGAgPSBuKCkpKSAlPiUgZmlsdGVyKGBBbnphaGwgU2NodWxlbmAgPT0gYE1pdCBLYXBheml0w6R0YCkKYGBgCgrDnGJlcnByw7xmdW5nIG9iIGRpZSBMaXN0ZSBkZXIgU2NodWxlbiB1bmQgTGlzdGUgZGVyIFNjaHVsZW4gbWl0IEthcGF6aXTDpHRzaW5mb3JtYXRpb25lbiBnbGVpY2ggc2luZDoKCmBgYHtyfQpiZXppcmsgPSAnVGVtcGVsaG9mLVNjaMO2bmViZXJnJwpzY2h1bGVuX21pdF9rYXBhID0ga2FwYXMgJT4lIGZpbHRlcihCZXppcmsgPT0gYmV6aXJrKSAlPiUgLiRTY2h1bG51bW1lcgpzY2h1bGVuX21pdF9rYXBhCmdydW5kc2NodWxlbiA9IHJlX3NjaHVsc3RhbmQgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIGZpbHRlcihCRVpJUksgPT0gYmV6aXJrKSAlPiUgLiRzcGF0aWFsX25hbWUKJ0luIEFubWVsZGVsaXN0ZSwgZmVobHQgaW4gU2NodWxzdGFuZCcKc2V0ZGlmZihzY2h1bGVuX21pdF9rYXBhLCBncnVuZHNjaHVsZW4pCidJbiByZV9zY2h1bHN0YW5kLCBmZWhsdCBpbiBBbm1lbGRlbGlzdGUnCnNldGRpZmYoZ3J1bmRzY2h1bGVuLCBzY2h1bGVuX21pdF9rYXBhKQpgYGAKCgpgYGB7cn0KbWFwID0gZ2V0X21hcCgnQmVybGluJykKYGBgCgoKYGBge3J9CnJlX3NjaHVsc3RhbmRfZGZfd19rYXBhcyA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGxlZnRfam9pbihrYXBhcywgYnk9Yygnc3BhdGlhbF9uYW1lJz0nU2NodWxudW1tZXInKSkKYGBgCgpQbG90IGFsbGVyIFNjaHVsZW4sIG1pdCBkZXIgSW5mbywgb2IgS2FwYXppdMOkdHNpbmZvcm1hdGlvbmVuIHZlcmbDvGdiYXIgc2luZC4KCmBgYHtyfQpkYXRhID0gcmVfc2NodWxzdGFuZF9kZl93X2thcGFzICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgZmlsdGVyKEJFWklSSz09YmV6aXJrKSAlPiUgbXV0YXRlKG1pc3NpbmcuY2FwYT1pcy5uYShgUGzDpHR6ZWApKQpnZ21hcChtYXApICsgZ2VvbV9wb2ludChhZXMobG9uLCBsYXQsIGNvbG9yPW1pc3NpbmcuY2FwYSksIGRhdGE9ZGF0YSkgKwogICAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb24pLTAuMDEsIG1heChkYXRhJGxvbikrMC4wMSksIHlsaW09YyhtaW4oZGF0YSRsYXQpLTAuMDEsIG1heChkYXRhJGxhdCkrMC4wMSkpCmBgYAoKRmlsdGVyIGF1ZiBTY2h1bGVuIG1pdCBLYXBheml0w6R0c2luZm9ybWF0aW9uZW4gKGbDvHIgVC1TIHNpbmQgZGFzIGFsbGUpOgoKYGBge3J9CnJlbGV2YW50X3NjaG9vbHMgPSByZV9zY2h1bHN0YW5kX2RmX3dfa2FwYXMgJT4lIGZpbHRlcihncmVwbCgnRycsIHNwYXRpYWxfbmFtZSkpICU+JSBmaWx0ZXIoQkVaSVJLPT1iZXppcmsgJiAhaXMubmEoYFBsw6R0emVgKSkgJT4lIC4kc3BhdGlhbF9uYW1lCnJlbGV2YW50X3NjaG9vbHMKYGBgCgojIyBNYXBwaW5nIEJlemlyay0+TE9SLT5CbG9jawoKYGBge3J9CmRmX2JleiA9IGFzLmRhdGEuZnJhbWUoYmV6KQpkZl9sb3IgPSBhcy5kYXRhLmZyYW1lKGxvcikKZGZfYmxrID0gYXMuZGF0YS5mcmFtZShibGspCmBgYAoKIyMjIFNhbml0eS1DaGVjazogTE9ScyB1bmQgQmzDtmNrZSBpbSBCZXppcmsKCmBgYHtyfQpiZXpfaWQgPSBmaWx0ZXIoZGZfYmV6LCBCRVpOQU1FID09IGJlemlyaykkQkVaCnJlbGV2YW50X2xvcnMgPSBkZl9sb3IgJT4lIGZpbHRlcihCRVogPT0gYmV6X2lkKQpyZWxldmFudF9ibGtzID0gZGZfYmxrICU+JSBmaWx0ZXIoQkVaID09IGJlel9pZCkKYGBgCgpgYGB7cn0KZ2dwbG90KCkgKyBnZW9tX3BhdGgoYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwKSwgZGF0YT1sb3JbbG9yJEJFWiA9PSBiZXpfaWQsXSkgKyBjb29yZF9tYXAoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWJleiwgY29sb3I9J3JlZCcpCmBgYAoKIyMjIEJsw7Zja2UgaW0gQmV6aXJrCgpgYGB7cn0KZ2dwbG90KCkgKyBnZW9tX3BhdGgoYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwKSwgZGF0YT1ibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSkgKyBjb29yZF9tYXAoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWJleltiZXokQkVaID09IGJlel9pZCxdLCBjb2xvcj0ncmVkJykKYGBgCgojIyBLaW5kZXIgaW0gQmV6aXJrIGF1ZiBCbMO2Y2tlIGhvY2hyZWNobmVuCgrDnGJlciBkaWUgRWlud29obmVyaW5mb3JtYXRpb25lbiBpbiBgUkJTX09EX0JMS18yMDE1XzEyLmdlb2pzb25gIGthbm4gYEVXUjIwMTUxMkVfTWF0cml4LmNzdmAgdm9uIExPUi1FYmVuZSBhdWYgQmxvY2tlYmVuZSBob2NoZ2VyZWNobmV0IHdlcmRlbi4KClRPRE86IFN0YXR0ZGVzc2VuIG1pdCBodHRwczovL2dpdGh1Yi5jb20vYmVybGluZXJtb3JnZW5wb3N0L2NvZ3JhbiBtYWNoZW4/CgojIyMgUGxvdCBkZXIgNi1Kw6RocmlnZW4gbmFjaCBgRVdSMjAxNTEyRV9NYXRyaXguY3N2YAoKV2lyIHZlcndlbmRlbiBkYXMgbWl0dGVsIGRlciA1LSB1bmQgNi1Kw6RocmlnZW4uCgpUT0RPIG5ldWUgRGF0ZW4gdm9uIFRvcnJlcz8KVE9ETyBQcm9nbm9zZT8KCmBgYHtyfQpyZWxldmFudF9ld3IgPSBlaW53b2huZXJfbG9yICU+JQogIHNlbGVjdChSQVVNSUQsIEVfRTA1XzA2LCBFX0UwNl8wNykgJT4lCiAgZmlsdGVyKFJBVU1JRCAlaW4lIHJlbGV2YW50X2xvcnMkUExSKSAlPiUKICAjIFNjaG5pdHQgZGVyIDUgdW5kIDYtSsOkaHJpZ2VuCiAgbXV0YXRlKGtpZHM9KGFzLm51bWVyaWMoZ3N1YignLCcsJy4nLEVfRTA2XzA3KSkrYXMubnVtZXJpYyhnc3ViKCcsJywnLicsRV9FMDVfMDYpKSkvMikgJT4lIGFzLmRhdGEuZnJhbWUoKQoKZGF0YSA9IHRpZHkobG9yW2xvciRCRVogPT0gYmV6X2lkLF0sIHJlZ2lvbj0nUExSJykgJT4lIGlubmVyX2pvaW4ocmVsZXZhbnRfZXdyLCBieT1jKCdpZCc9J1JBVU1JRCcpKQpnZ21hcChtYXApICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1raWRzKSwgZGF0YT1kYXRhKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgojIyMgUGxvdCBkZXIgRWlud29obmVyIGF1ZiBCbG9ja2ViZW5lCgpgYGB7cn0KZGF0YSA9IHRpZHkoYmxrW2JsayRCRVogPT0gYmV6X2lkLF0sIHJlZ2lvbj0nQkxLJykgJT4lIGlubmVyX2pvaW4oZGZfYmxrLCBieT1jKCdpZCc9J0JMSycpKSAlPiUgbXV0YXRlKEVpbnc9aWZlbHNlKEVpbnc9PTAsIE5BLCBFaW53KSkKMApnZ21hcChtYXApICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1FaW53KSwgZGF0YT1kYXRhKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgoKIyMjIEhvY2hycmVjaG51bmcgYXVmIEJsw7Zja2UsIFN0cnVrdHVycXVvdGUKCmBgYHtyfQpTdHJ1a3R1cnF1b3RlID0gMC45CgpraWRzX2luX2Jsa3MgPSByZWxldmFudF9ibGtzICU+JQogIGdyb3VwX2J5KFBMUikgJT4lCiAgbXV0YXRlKEVpbndSYXRpbyA9IEVpbncvc3VtKEVpbncpKSAlPiUKICB1bmdyb3VwICU+JQogIGxlZnRfam9pbihyZWxldmFudF9ld3IsIGJ5PWMoJ1BMUic9J1JBVU1JRCcpKSAlPiUKICBtdXRhdGUoa2lkcyA9IEVpbndSYXRpbypraWRzKSAlPiUKICBtdXRhdGUoa2lkcyA9IFN0cnVrdHVycXVvdGUqa2lkcykgJT4lICMgU3RydWt0dXJxdW90ZQogIHNlbGVjdChCRVosIFBMUiwgQkxLLCBFaW53LCBraWRzKSAlPiUKICBhcy5kYXRhLmZyYW1lKCkKcm93Lm5hbWVzKGtpZHNfaW5fYmxrcykgPSBraWRzX2luX2Jsa3MkQkxLCgpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihraWRzX2luX2Jsa3MsIGJ5PWMoJ2lkJz0nQkxLJykpICU+JSBtdXRhdGUoa2lkcz1pZmVsc2Uoa2lkcz09MCwgTkEsIGtpZHMpLCBFaW53PWlmZWxzZShFaW53PT0wLCBOQSwgRWludykpCgpnZ21hcChtYXApICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1raWRzKSwgZGF0YT1kYXRhKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgojIyBWZXJmw7xnYmFyZSBQbMOkdHplCgpgYGB7cn0KcmVsZXZhbnRfa2FwYXMgPSBrYXBhcyAlPiUgc2VsZWN0KFNjaHVsbnVtbWVyLCBLYXBhPWBQbMOkdHplYCkgJT4lIGZpbHRlcihTY2h1bG51bW1lciAlaW4lIHJlbGV2YW50X3NjaG9vbHMpICU+JSBhcy5kYXRhLmZyYW1lKCkKI3Jvdy5uYW1lcyhyZWxldmFudF9rYXBhcykgPSByZWxldmFudF9rYXBhcyRTY2h1bG51bW1lcgpgYGAKCiMjIyDDnGJlcnByw7xmdW5nIGRlciBTdW1tZSBkZXIgS2FwYXppdMOkdGVuLCBBbm1lbGR1bmdlbiB1bmQgS2luZGVyc3RhdGlzdGlrZW4KCmBgYHtyfQonU3VtbWUgS2FwYXMnCnJlbGV2YW50X2thcGFzICU+JSAuJEthcGEgJT4lIHN1bQonQW5tZWxkdW5nZW4nCmthcGFzICU+JSBtdXRhdGUoQW5tZWxkdW5nZW4gPSBhcy5udW1lcmljKGdzdWIoJ1teMC05XScsICcnLCBBbm1lbGR1bmdlbikpKSAlPiUgZmlsdGVyKFNjaHVsbnVtbWVyICVpbiUgcmVsZXZhbnRfc2Nob29scykgJT4lIC4kQW5tZWxkdW5nZW4gJT4lIHN1bQonS2lkcyBsYXV0IFN0YXRpc3RpaycKa2lkc19pbl9ibGtzJGtpZHMgJT4lIHN1bQpyZWxldmFudF9ld3Ika2lkcyAlPiUgc3VtCmBgYAoKIyMgU2NodWx3ZWdlIHZvbiBCbMO2Y2tlbiB6dSBTY2h1bGVuIGFnZ3JlZ2llcmVuCgpGw7xyIGplZGVzIFdvaG5nZWLDpHVkZSBzdWNoZW4gd2lyIGRlbiB6dWdlaMO2cmlnZW4gQmxvY2sKCmBgYHtyfQpyZXNpZGVudGlhbF9idWlsZGluZ3NfYmxvY2tzID0gc2FtcGxlZF9idWlsZGluZ3MgJT4lIGlubmVyX2pvaW4oZGZfYmxrKSAlPiUgZmlsdGVyKEJFWiA9PSBiZXpfaWQpCnJlc2lkZW50aWFsX2J1aWxkaW5nc19ibG9ja3MKYGBgCgpgYGB7cn0Kcm91dGVzX2Zyb21fYmxrcyA9IHJlc2lkZW50aWFsX2J1aWxkaW5nc19ibG9ja3MgJT4lCiAgbGVmdF9qb2luKHJvdXRlX21hdHJpeCAlPiUgZmlsdGVyKGRzdCAlaW4lIHJlbGV2YW50X3NjaG9vbHMpLCBieT1jKCdPSSc9J3NyYycpKQpoZWFkKHJvdXRlc19mcm9tX2Jsa3MpCmBgYAoKIyMjIFBsb3QgZGVyIHJlbGV2YW50ZW4gQmzDtmNrZSAobWl0IFdvaG5nZWLDpHVkZW4pCgpgYGB7cn0KZGF0YSA9IHRpZHkoYmxrW2JsayRCRVogPT0gYmV6X2lkLF0sIHJlZ2lvbj0nQkxLJykgJT4lIGlubmVyX2pvaW4ocm91dGVzX2Zyb21fYmxrcyAlPiUgZ3JvdXBfYnkoQkxLKSAlPiUgc3VtbWFyaXNlKG49bigpKSwgYnk9YygnaWQnPSdCTEsnKSkKZ2dtYXAobWFwKSArIGdlb21fcG9seWdvbihhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBmaWxsPSdyZWQnLCBkYXRhPWRhdGEpICsKICAjZ2VvbV9wb2ludChhZXMoeD1sb24sIHk9bGF0KSwgZGF0YT1yYl9kZiwgY29sb3I9J2JsYWNrJywgc2l6ZT0wLjAxKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgoKYGBge3J9CnRyYXZlbF9mcm9tX2Jsa3MgPSByb3V0ZXNfZnJvbV9ibGtzICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIGdyb3VwX2J5KEJMSywgZHN0KSAlPiUgc3VtbWFyaXNlKG1pbj1taW4oZGlzdGFuY2UpLCBhdmc9bWVhbihkaXN0YW5jZSksIG1lZD1tZWRpYW4oZGlzdGFuY2UpLCBtYXg9bWF4KGRpc3RhbmNlKSkgJT4lIHVuZ3JvdXAKdHJhdmVsX2Zyb21fYmxrcwpgYGAKCiMjIyBQbG90IGRlciBCbMO2Y2tlIG1pdCBGw6RyYnVuZyBuYWNoIGR1cmNoc2Nobml0dGxpY2hlbSBXZWcgenVyIG7DpGNoc3RlbiBTY2h1bGUKCmBgYHtyfQpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgbGVmdF9qb2luKHRyYXZlbF9mcm9tX2Jsa3MgJT4lIGdyb3VwX2J5KEJMSykgJT4lIHRvcF9uKDEsIC1hdmcpLCBieT1jKCdpZCc9J0JMSycpKQpnZ21hcChtYXApICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD0tYXZnKSwgZGF0YT1kYXRhKSArCiAgZ2VvbV9wb2ludChhZXMobG9uLCBsYXQpLCBjb2xvcj0ncmVkJywgZGF0YSA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGZpbHRlcihCRVpJUks9PWJlemlyayAmIFNDSFVMQVJUPT0nR3J1bmRzY2h1bGUnKSkgKwogIGNvb3JkX21hcCh4bGltPWMobWluKGRhdGEkbG9uZyktMC4wMSwgbWF4KGRhdGEkbG9uZykrMC4wMSksIHlsaW09YyhtaW4oZGF0YSRsYXQpLTAuMDEsIG1heChkYXRhJGxhdCkrMC4wMSkpCmBgYAoKCmBgYHtyfQp0cmF2ZWxfbWF0cml4ID0gdHJhdmVsX2Zyb21fYmxrcyAlPiUgc2VsZWN0KEJMSywgZHN0LCBhdmcpICU+JSBzcHJlYWQoZHN0LCBhdmcpCmRpbSh0cmF2ZWxfbWF0cml4KQp0cmF2ZWxfbWF0cml4CmBgYAoKIyMgU296aW/Dtmtvbm9taXNjaGUgRGF0ZW4KCldpciBoYWJlbiBTb3ppb8O2a29ub21pc2NoZSBEYXRlbiBpbiBkZW4gV2FobGJlemlya2VuLiBTdHJhdGVnaWU6Ci0gU2NobmVpZGVuIGRlciBXYWhsYmV6aXJrZSBtaXQgZGVuIEJsw7Zja2VuCi0gw5xiZXJuYWhtZSBkZXMgUHJvemVudHdlcnRlcyB2b20gV2FobGJlemlyayBmw7xyIGplZGVuIChVbnRlci0pYmxvY2sKLSBadW9yZG51bmcgenUgamVkZW0gQmxvY2sgdW5kIFZlcmVpbmlndW5nIGR1cmNoIEZsw6RjaGVuL1dvaG5oYXVzLWdld2ljaHRldGVzIE1pdHRlbCBkZXMgUHJvemVudHdlcnRlcyAKCmBgYHtyfQpVV0IgPSByZWFkT0dSKCdkb3dubG9hZC9SQlNfT0RfVVdCX0FHSDIwMTYuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpClVXQiRJRCA9IHBhc3RlMChVV0IkQkVaLCBVV0IkVVdCKQpzb3ppb19VV0IgPSByZWFkX2V4Y2VsKCdkb3dubG9hZC9ETF9CRV9BSDIwMTZfU3RydWt0dXJkYXRlbi54bHN4Jywgc2hlZXQgPSAzKSAlPiUgc2VsZWN0KElELCBzZ2JJSXU2NT1gRWlud29obmVyIHVudGVyIDY1IGluIFNHQiBJSSAyMDE0IFByb3plbnRgKQpVV0IgPSBVV0IgJT4lIHNwOjptZXJnZShzb3ppb19VV0IsIGJ5PSdJRCcpCgpnZ3Bsb3QoYnJvb206OnRpZHkoVVdCLCByZWdpb249J0lEJykgJT4lIGlubmVyX2pvaW4oVVdCICU+JSBhcy5kYXRhLmZyYW1lLCBieT1jKCdpZCc9J0lEJykpKSArIGdlb21fcG9seWdvbihhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXAsIGZpbGw9c2diSUl1NjUpKSArIGNvb3JkX21hcCgpCmBgYAoKQ2hlY2sgZm9yIHNlbGYgaW50ZXJzZWN0aW9ucwpgYGB7cn0Kd2dzODQgPSBDUlMocHJvajRzdHJpbmcoYmxrKSkKZWFfcHJvamVjdGlvbiA9IENSUygiK3Byb2o9bGFlYSArbGF0XzA9NTIgK2xvbl8wPTEwICt4XzA9NDMyMTAwMCAreV8wPTMyMTAwMDAgK2VsbHBzPUdSUzgwICt0b3dnczg0PTAsMCwwLDAsMCwwLDAgK3VuaXRzPW0gK25vX2RlZnMiKQpibGtfaW5fYmV6ID0gYmxrW2JsayRCRVogPT0gYmV6X2lkLF0KZ0lzVmFsaWQoYmxrX2luX2JleikKYmxrX2luX2JleiA9IGdCdWZmZXIoc3BUcmFuc2Zvcm0oYmxrX2luX2JleiwgZWFfcHJvamVjdGlvbiksIGJ5aWQ9VCwgd2lkdGggPSAtMC4xKQphbnkoeG9yKGdJbnRlcnNlY3RzKGJsa19pbl9iZXosIGJ5aWQ9VCwgcmV0dXJuRGVuc2UgPSBUKSwgZGlhZygxLCBsZW5ndGgoYmxrX2luX2JleikpID09IDEpKQpwbG90KGJsa19pbl9iZXopCiNnSW50ZXJzZWN0cyhVV0JbVVdCJEJFWiA9PSAnMDcnLF0sIGJ5aWQ9VFJVRSkKYGBgCgpgYGB7cn0KdXdiX2luX2JleiA9IGdCdWZmZXIoc3BUcmFuc2Zvcm0oVVdCW1VXQiRCRVogPT0gJzA3JyxdLCBlYV9wcm9qZWN0aW9uKSwgYnlpZD1ULCB3aWR0aD0tMC4xKQpnSXNWYWxpZCh1d2JfaW5fYmV6KQphbnkoeG9yKGdJbnRlcnNlY3RzKHV3Yl9pbl9iZXosIGJ5aWQ9VCwgcmV0dXJuRGVuc2UgPSBUKSwgZGlhZygxLCBsZW5ndGgodXdiX2luX2JleikpID09IDEpKQpwbG90KHV3Yl9pbl9iZXopCmBgYAoKYGBge3J9CmludGVyc2VjdGlvbiA9IGdJbnRlcnNlY3Rpb24odXdiX2luX2JleiwgYmxrX2luX2JleiwgYnlpZCA9IFQsIGRyb3BfbG93ZXJfdGQgPSBUKQoKZ0lzVmFsaWQoaW50ZXJzZWN0aW9uKQpwbG90KGludGVyc2VjdGlvbikKYGBgCgpgYGB7cn0KaW50ZXJzZWN0aW9uX3V3Yl9kYXRhID0gaW50ZXJzZWN0aW9uICU+JSBvdmVyKHV3Yl9pbl9iZXopCmludGVyc2VjdGlvbl9ibGtfZGF0YSA9IGludGVyc2VjdGlvbiAlPiUgb3ZlcihibGtfaW5fYmV6KQppbnRlcnNlY3Rpb25fYXJlYSA9IGdBcmVhKGludGVyc2VjdGlvbiwgYnlpZD1UKQppbnRlcnNlY3Rpb25fZGF0YSA9IGNiaW5kKAogICAgaW50ZXJzZWN0aW9uX3V3Yl9kYXRhICU+JSBzZWxlY3QoVVdCX0lEPUlELCBzZ2JJSXU2NSksCiAgICBpbnRlcnNlY3Rpb25fYmxrX2RhdGEgJT4lIHNlbGVjdChCTEssIEJMS19FaW53PUVpbncpLAogICAgZGF0YS5mcmFtZShhcmVhPWludGVyc2VjdGlvbl9hcmVhKSAjIEZJWE1FIGhvdyBlbHNlIHRvIG5vcm1hbGl6ZT8KICAgICkKYGBgCgpgYGB7cn0KbGVuZ3RoKGludGVyc2VjdGlvbikKbnJvdyhibGtfaW5fYmV6KQojIGRpZCB3ZSBtaXNzIGFueSBibG9ja3M/CnNldGRpZmYoYmxrX2luX2JleiRCTEssIGludGVyc2VjdGlvbl9kYXRhJEJMSykKYGBgCgpQcm8gQmxvY2sgbWlzY2hlIGRpZSBTR0ItV2VydGUgZGVyIFVudGVyYmzDtmNrZSBnZXdpY2h0ZXQgbmFjaCBGbMOkY2hlLgpGSVhNRSAtIGJlc3NlciBuYWNoIEFuemFobCBkZXIgV29obmjDpHVzZXI/CmBgYHtyfQpzZ2JJSV9ibGsgPSBpbnRlcnNlY3Rpb25fZGF0YSAlPiUKICBncm91cF9ieShCTEspICU+JQogIHN1bW1hcmlzZShzZ2JJSXU2NT1zdW0oc2diSUl1NjUqYXJlYSkvc3VtKGFyZWEpLzEwMCkKYGBgCgoKYGBge3J9CmdncGxvdChicm9vbTo6dGlkeShzcFRyYW5zZm9ybShibGtfaW5fYmV6LCB3Z3M4NCksIHJlZ2lvbj0nQkxLJykgJT4lIGxlZnRfam9pbihzZ2JJSV9ibGssIGJ5PWMoJ2lkJz0nQkxLJykpKSArIGdlb21fcG9seWdvbihhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXAsIGZpbGw9c2diSUl1NjUpKSArIGNvb3JkX21hcCgpCmBgYAoKIyMgQWRqYXplbnogdm9uIEJsw7Zja2VuCgpgYGB7cn0Kcm93Lm5hbWVzKGJsa19pbl9iZXopID0gYmxrX2luX2JleiRCTEsKYnVmZmVkZWRfYmxrX2luX2JleiA9IGdCdWZmZXIoYmxrX2luX2JleiwgYnlpZD1ULCB3aWR0aD00MCkKYWRqYWNlbmN5ID0gZ0ludGVyc2VjdHMoZ0J1ZmZlcihibGtfaW5fYmV6LCBieWlkPVQsIHdpZHRoPTQwKSwgYnlpZCA9IFQpCnJvd25hbWVzKGFkamFjZW5jeSkgPSBibGtfaW5fYmV6JEJMSwpjb2xuYW1lcyhhZGphY2VuY3kpID0gYmxrX2luX2JleiRCTEsKCmFkamFjZW5jeV9kZiA9IGFkamFjZW5jeSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBtdXRhdGUoZnJvbT1yb3duYW1lcyguKSkgJT4lCiAgZ2F0aGVyKHRvLCBjb25uZWN0ZWQsIC1mcm9tKSAlPiUgZmlsdGVyKGNvbm5lY3RlZCkgJT4lCiAgaW5uZXJfam9pbihzcFRyYW5zZm9ybShibGtfaW5fYmV6LCB3Z3M4NCkgJT4lIGNvb3JkaW5hdGVzKCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgcmVuYW1lKGZyb21fbG9uZz1WMSwgZnJvbV9sYXQ9VjIpICU+JSBtdXRhdGUoZnJvbT1yb3duYW1lcyguKSkpICU+JQogIGlubmVyX2pvaW4oc3BUcmFuc2Zvcm0oYmxrX2luX2Jleiwgd2dzODQpICU+JSBjb29yZGluYXRlcygpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHJlbmFtZSh0b19sb25nPVYxLCB0b19sYXQ9VjIpICU+JSBtdXRhdGUodG89cm93bmFtZXMoLikpKQoKZ2dwbG90KCkgKwogIGdlb21fcG9seWdvbihhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBmaWxsPSdncmF5JywgZGF0YT1icm9vbTo6dGlkeShzcFRyYW5zZm9ybShibGtfaW5fYmV6LCB3Z3M4NCksIHJlZ2lvbj0nQkxLJykpICsgCiAgZ2VvbV9zZWdtZW50KGFlcyh4PWZyb21fbG9uZywgeT1mcm9tX2xhdCwgeGVuZD10b19sb25nLCB5ZW5kPXRvX2xhdCksIHNpemU9MC4xLCBjb2xvcj0nYmxhY2snLCBkYXRhPWFkamFjZW5jeV9kZikgKwogIHRoZW1lX25vdGhpbmcoKSArIGNvb3JkX21hcCgpCgpnZ3NhdmUoJ2ZpZ3MvYWRqYWNlbmN5LnBkZicpCmFkamFjZW5jeV9kZiAlPiUgZmlsdGVyKGNvbm5lY3RlZCAmIGZyb20gIT0gdG8pICU+JSBzZWxlY3QoZnJvbSwgdG8pICU+JSB3cml0ZV9jc3YoJ2FwcC9kYXRhL2FkamFjZW5jeS5jc3YnKQpgYGAKCgojIyBTZWxlY3QgcmVsZXZhbnQgZGF0YQoKYGBge3J9Cm9wdGltX2thcGFzID0gcmVsZXZhbnRfa2FwYXMKb3B0aW1fa2lkc19pbl9ibGtzID0ga2lkc19pbl9ibGtzICU+JSBmaWx0ZXIoa2lkcyA+IDApICU+JSBpbm5lcl9qb2luKHRyYXZlbF9tYXRyaXgsIGJ5PSdCTEsnKSAlPiUgc2VsZWN0KEJMSywga2lkcykgJT4lIG11dGF0ZShraWRzPWtpZHMpCm5yb3cob3B0aW1fa2lkc19pbl9ibGtzKQpucm93KG9wdGltX2thcGFzKQoKc2VsZWN0X3NjaG9vbHMgPSBhcy5jaGFyYWN0ZXIob3B0aW1fa2FwYXMkU2NodWxudW1tZXIpCnNlbGVjdF9ibGtzID0gYXMuY2hhcmFjdGVyKG9wdGltX2tpZHNfaW5fYmxrcyRCTEspCgpvcHRpbV9tYXRyaXggPSBpbm5lcl9qb2luKG9wdGltX2tpZHNfaW5fYmxrcywgdHJhdmVsX21hdHJpeCwgYnk9J0JMSycpW3NlbGVjdF9zY2hvb2xzXQoKZGltKG9wdGltX21hdHJpeCkKCm9wdGltX2thcGFzJEthcGEgJT4lIHN1bQpvcHRpbV9raWRzX2luX2Jsa3Mka2lkcyAlPiUgc3VtCmBgYAoKIyMgTmFpdmUgWnVvcmRudW5nOiBKZWRlciBCbG9jayB6dXIgbsOkY2hzdGVuIFNjaHVsZQoKYGBge3J9CnNvbHV0aW9uID0gb3B0aW1fbWF0cml4ICU+JSBtdXRhdGUoQkxLPW9wdGltX2tpZHNfaW5fYmxrcyRCTEspICU+JSBnYXRoZXIoc2Nob29sLCBkaXN0LCAtQkxLKSAlPiUgZ3JvdXBfYnkoQkxLKSAlPiUgdG9wX24oMSwgLWRpc3QpICU+JSB1bmdyb3VwCgpvcHRpbV9tYXRyaXggJT4lIHQgJT4lIGFzLmRhdGEuZnJhbWUgJT4lIHN1bW1hcmlzZV9lYWNoKGZ1bnMobWluKSkgJT4lIHN1bSgpCgpzb2xpbmVzID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgaW5uZXJfam9pbihzb2x1dGlvbiwgYnk9Yygnc3BhdGlhbF9uYW1lJz0nc2Nob29sJykpICU+JSBpbm5lcl9qb2luKGNiaW5kKGFzLmRhdGEuZnJhbWUoY29vcmRpbmF0ZXMoYmxrW2JsayRCRVogPT0gYmV6X2lkLF0pKSwgYmxrW2JsayRCRVogPT0gYmV6X2lkLF1AZGF0YVsnQkxLJ10pKQoKZGF0YSA9IHRpZHkoYmxrW2JsayRCRVogPT0gYmV6X2lkLF0sIHJlZ2lvbj0nQkxLJykgJT4lIGxlZnRfam9pbihzb2x1dGlvbiwgYnk9YygnaWQnPSdCTEsnKSkKZ2dtYXAobWFwLCBkYXJrZW4gPSBjKDAuNSwgJ3doaXRlJykpICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1zY2hvb2wpLCBkYXRhPWRhdGEpICsKICBnZW9tX3NlZ21lbnQoYWVzKHg9VjEseT1WMix4ZW5kPWxvbix5ZW5kPWxhdCksIGRhdGE9c29saW5lcywgc2l6ZT0wLjMpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCksIGNvbG9yPSdibGFjaycsIHNpemU9MiwgZGF0YSA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGlubmVyX2pvaW4oc29sdXRpb24sIGJ5PWMoJ3NwYXRpYWxfbmFtZSc9J3NjaG9vbCcpKSkgKwogIGdlb21fcG9pbnQoYWVzKGxvbiwgbGF0LCBjb2xvcj1zcGF0aWFsX25hbWUpLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgaW5uZXJfam9pbihzb2x1dGlvbiwgYnk9Yygnc3BhdGlhbF9uYW1lJz0nc2Nob29sJykpKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkgKwogIGd1aWRlcyhjb2xvcj1GLCBmaWxsPUYpCmBgYAoKIyMgRGFyc3RlbGx1bmcgZGVyIFp1b3JkbnVuZyBhbHMgVGFiZWxsZQoKYGBge3J9CmxpYnJhcnkoZm9ybWF0dGFibGUpCmBgYAoKYGBge3J9CnNvbHV0aW9uICU+JSBpbm5lcl9qb2luKG9wdGltX2tpZHNfaW5fYmxrcywgYnk9J0JMSycpICU+JSBpbm5lcl9qb2luKHRyYXZlbF9mcm9tX2Jsa3MsIGJ5PWMoJ0JMSyc9J0JMSycsICdzY2hvb2wnPSdkc3QnKSkgJT4lCiAgZ3JvdXBfYnkoc2Nob29sKSAlPiUgc3VtbWFyaXNlKAogICAga2lkcz1zdW0oa2lkcyksCiAgICBudW1fYmxvY2tzPW4oKSwKICAgIG1pbl9kaXN0PW1pbihtaW4pLAogICAgYXZnX2Rpc3Q9bWVhbigoa2lkcyphdmcpL3N1bShraWRzKSksCiAgICBtYXhfZGlzdD1tYXgobWF4KQogICkgJT4lCiAgaW5uZXJfam9pbihyZWxldmFudF9rYXBhcywgYnk9Yygnc2Nob29sJz0nU2NodWxudW1tZXInKSkgJT4lCiAgbXV0YXRlKAogICAgdXRpbGl6YXRpb249a2lkcy9LYXBhCiAgKSAlPiUgc2VsZWN0KAogICBTY2h1bGU9c2Nob29sLAogICBgQmzDtmNrZWA9bnVtX2Jsb2NrcywKICAgS2FwYXppdMOkdD1LYXBhLAogICBLaW5kZXI9a2lkcywKICAgQXVzbGFzdHVuZz11dGlsaXphdGlvbiwKICAgYFdlZyAobWluKWA9bWluX2Rpc3QsCiAgIGBXZWcgKMOYKWA9YXZnX2Rpc3QsCiAgIGBXZWcgKG1heClgPW1heF9kaXN0CiAgKSAlPiUKICBmb3JtYXR0YWJsZSgKICAgIGxpc3QoCiAgICAgIEtpbmRlciA9IGZvcm1hdHRlcigic3BhbiIsIHggfiBkaWdpdHMoeCwgMikpLAogICAgICBBdXNsYXN0dW5nID0gZm9ybWF0dGVyKCJzcGFuIiwKICAgICAgICBzdHlsZSA9IHggfiBzdHlsZShjb2xvciA9IGlmZWxzZSh4IDwgMSwgImdyZWVuIiwgInJlZCIpKSwKICAgICAgICB4IH4gaWNvbnRleHQoaWZlbHNlKHggPCAxLCAib2siLCAicmVtb3ZlIiksIHBlcmNlbnQoeCkpCiAgICAgICksCiAgICAgIGBXZWcgKMOYKWAgPSBwcm9wb3J0aW9uX2JhcigibGlnaHRibHVlIiksCiAgICAgIGBXZWcgKG1pbilgID0gcHJvcG9ydGlvbl9iYXIoImxpZ2h0Ymx1ZSIpLAogICAgICBgV2VnIChtYXgpYCA9IHByb3BvcnRpb25fYmFyKCJsaWdodGJsdWUiKQogICAgKQogICkKYGBgCgoKIyMgRGF0ZW4gZsO8ciBkaWUgQXBwIHNwZWljaGVybgoKLSBlbnRpdGllcy5nZW9qc29uCi0gZW50aXRpZXMuY3N2Ci0gdW5pdHMuZ2VvanNvbgotIHVuaXRzLmNzdgotIHdlaWdodHMuY3N2Ci0gYXNzaWdubWVudC5jc3YKCiMjIyBOZXVlIERhdGVuCgoKIyMjIyBFbnRpdGllcyAvIFNjaHVsZW4KCmBgYHtyfQplbnRpdGllcyA9IHN1YnNldChyZV9zY2h1bHN0YW5kX2RmLCBzcGF0aWFsX25hbWUgJWluJSByZWxldmFudF9zY2hvb2xzKSAlPiUKICByZW5hbWUoZW50aXR5X2lkID0gc3BhdGlhbF9uYW1lKSAlPiUKICBzZWxlY3QoLWdtbF9pZCwgLXNwYXRpYWxfYWxpYXMsIC1zcGF0aWFsX3R5cGUpICU+JQogIGlubmVyX2pvaW4ocmVuYW1lKHJlbGV2YW50X2thcGFzLCBjYXBhY2l0eT1LYXBhKSwgYnk9YygnZW50aXR5X2lkJz0nU2NodWxudW1tZXInKSkKY29vcmRpbmF0ZXMoZW50aXRpZXMpID0gfiBsb24gKyBsYXQKcHJvajRzdHJpbmcoZW50aXRpZXMpID0gQ1JTKCIrcHJvaj1sb25nbGF0ICtlbGxwcz1XR1M4NCArZGF0dW09V0dTODQgK25vX2RlZnMiKQpmaWxlLnJlbW92ZSgnYXBwL2RhdGEvZW50aXRpZXMuZ2VvanNvbicpCmVudGl0aWVzICU+JSB3cml0ZU9HUignYXBwL2RhdGEvZW50aXRpZXMuZ2VvanNvbicsIGxheWVyPSJlbnRpdGllcyIsIGRyaXZlcj0iR2VvSlNPTiIsIGNoZWNrX2V4aXN0cz1GKQplbnRpdGllc0BkYXRhICU+JSBzZWxlY3QoZW50aXR5X2lkLCBjYXBhY2l0eSkgJT4lIHdyaXRlX2NzdignYXBwL2RhdGEvZW50aXRpZXMuY3N2JykKYGBgCgojIyMjIFVuaXRzIC8gU3RhdGlzdGlzY2hlIEJsw7Zja2UKCmBgYHtyfQp1bml0cyA9IHN1YnNldChibGssIEJFWiA9PSBiZXpfaWQpCgp1bml0c0BkYXRhJFBMUiA9IE5VTEwKdW5pdHNAZGF0YSRFaW53ID0gTlVMTAp1bml0c0BkYXRhJEJFWiA9IE5VTEwKdW5pdHNAZGF0YSR1bml0X2lkID0gdW5pdHNAZGF0YSRCTEsKdW5pdHNAZGF0YSRCTEsgPSBOVUxMCgp1bml0cyA9IHVuaXRzICU+JSBzcDo6bWVyZ2UoCiAgb3B0aW1fa2lkc19pbl9ibGtzICU+JQogICAgbGVmdF9qb2luKHNnYklJX2JsaykgJT4lCiAgICBzZWxlY3QodW5pdF9pZD1CTEssIHBvcHVsYXRpb249a2lkcywgc2diSUl1NjUpCiAgKQoKZmlsZS5yZW1vdmUoJ2FwcC9kYXRhL3VuaXRzLmdlb2pzb24nKQp1bml0cyAlPiUgd3JpdGVPR1IoJ2FwcC9kYXRhL3VuaXRzLmdlb2pzb24nLCBsYXllcj0idW5pdHMiLCBkcml2ZXI9Ikdlb0pTT04iLCBjaGVja19leGlzdHM9RikKdW5pdHNAZGF0YSAlPiUgd3JpdGVfY3N2KCdhcHAvZGF0YS91bml0cy5jc3YnKQpgYGAKCiMjIyMgWnVvcmRudW5nCgpgYGB7cn0KYXNzaWdubWVudCA9IHNvbHV0aW9uICU+JSBzZWxlY3QodW5pdF9pZD1CTEssIGVudGl0eV9pZD1zY2hvb2wpCmFzc2lnbm1lbnQgJT4lIHdyaXRlX2NzdignYXBwL2RhdGEvYXNzaWdubWVudC5jc3YnKQpgYGAKCiMjIyMgV2VpZ2h0cyAvIFNjaHVsd2VnZQoKYGBge3J9CnRyYXZlbF9mcm9tX2Jsa3MgJT4lCiAgcmVuYW1lKHVuaXRfaWQ9QkxLLCBlbnRpdHlfaWQ9ZHN0KSAlPiUKICAjZ2F0aGVyKHdlaWdodCwgdmFsdWUsIC11bml0X2lkLCAtZW50aXR5X2lkKSAlPiUKICB3cml0ZV9jc3YoJ2FwcC9kYXRhL3dlaWdodHMuY3N2JykKYGBgCgojIyMjIFp1c8OkdHpsaWNoZSBEYXRlbgoKYGBge3J9CmZpbGUuY29weSgnZG93bmxvYWQvUkJTX09EX0JFWl8yMDE1XzEyLmdlb2pzb24nLCAnYXBwL2RhdGEvUkJTX09EX0JFWl8yMDE1XzEyLmdlb2pzb24nKQpgYGAKCmBgYHtyfQphZGRyZXNzZXNfaW5fYmV6ID0gSEtPXzIwMTVbIWlzLm5hKG92ZXIoSEtPXzIwMTUsIHN1YnNldChibGssIEJFWiA9PSBiZXpfaWQpKSRCRVopLGMoJ1NUTicsICdITlInKV0KbmFtZXMoYWRkcmVzc2VzX2luX2JleikgPSBjKCdzdHJlZXQnLCAnbm8nKQphZGRyZXNzZXNfaW5fYmV6QGRhdGEKCmZpbGUucmVtb3ZlKCdhcHAvZGF0YS9hZGRyZXNzZXMuZ2VvanNvbicpCndyaXRlT0dSKGFkZHJlc3Nlc19pbl9iZXosICJhcHAvZGF0YS9hZGRyZXNzZXMuZ2VvanNvbiIsIGxheWVyPSJhZGRyZXNzZXMiLCBkcml2ZXI9Ikdlb0pTT04iLCBjaGVja19leGlzdHMgPSBGQUxTRSkKYGBgCgo=